/*
Copyright (c) 2009, Dan Hagerstrand and contributors
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of Cirrus Creative nor the names of its
  contributors may be used to endorse or promote products derived from this
  software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <cstring>
#include <string>
#include <bitset>
#include <allegro.h>

#define NUM_CPU_FAMILY 8
#define NUM_CPU_CAPS 12
#define NUM_CPU_OS 25

#define FPS_NUM_VALUES 5
#define FPS_UPDATE 1
#define FPS_NEWLINE 2

#include "../../include/beng/BengInterface.h"
#include "../../include/beng/System.h"

// Necessary cleanup for when the object is destroyed for some reason
BengInterface::~BengInterface(void)
{
    this->deinit();
    delete this->iGfx;
    delete this->iSfx;
}

// Required in game initialization
int BengInterface::init(void)
{
    char textrow[50] = "\0";
    int textpos = 0;
    int rowused = 0;
    int deskw;
    int deskh;

    BengSystem *system = this->system;

    BengTextIdMap cpufamily[NUM_CPU_FAMILY] =
    {
        { CPU_FAMILY_UNKNOWN,  "Unknown" },
        { CPU_FAMILY_I386,     "I386" },
        { CPU_FAMILY_I486,     "I486" },
        { CPU_FAMILY_I586,     "I586" },
        { CPU_FAMILY_I686,     "I686" },
        { CPU_FAMILY_ITANIUM,  "Itanium" },
        { CPU_FAMILY_POWERPC,  "PowerPC" },
        { CPU_FAMILY_EXTENDED, "Extended" },
    };

    BengTextIdMap cpucaps[NUM_CPU_CAPS] =
    {
        { CPU_ID,       "CPUID" },
        { CPU_FPU,      "FPU" },
        { CPU_IA64,     "IA64" },
        { CPU_AMD64,    "AMD64" },
        { CPU_MMX,      "MMX" },
        { CPU_MMXPLUS,  "MMX+" },
        { CPU_SSE,      "SSE" },
        { CPU_SSE2,     "SSE2" },
        { CPU_SSE3,     "SSE3" },
        { CPU_3DNOW,    "3DNow" },
        { CPU_ENH3DNOW, "E3DNow" },
        { CPU_CMOV,     "CMOV" },
    };

    BengTextIdMap cpuos[NUM_CPU_OS] =
    {
        { OSTYPE_UNKNOWN,  "Unknown/MSDOS" },
        { OSTYPE_WIN3,     "Windows 3.1" },
        { OSTYPE_WIN95,    "Windows 95" },
        { OSTYPE_WIN98,    "Windows 98" },
        { OSTYPE_WINME,    "Windows ME" },
        { OSTYPE_WINNT,    "Windows NT" },
        { OSTYPE_WIN2000,  "Windows 2000" },
        { OSTYPE_WINXP,    "Windows XP" },
        { OSTYPE_WIN2003,  "Windows 2003" },
        { OSTYPE_WINVISTA, "Windows Vista" },
        { OSTYPE_OS2,      "OS/2" },
        { OSTYPE_WARP,     "OS/2 Warp 3" },
        { OSTYPE_DOSEMU,   "Linux DOSEMU" },
        { OSTYPE_OPENDOS,  "Caldera OpenDOS" },
        { OSTYPE_LINUX,    "Linux" },
        { OSTYPE_SUNOS,    "SunOS/Solaris" },
        { OSTYPE_FREEBSD,  "FreeBSD" },
        { OSTYPE_NETBSD,   "NetBSD" },
        { OSTYPE_IRIX,     "IRIX" },
        { OSTYPE_DARWIN,   "Darwin" },
        { OSTYPE_QNX,      "QNX" },
        { OSTYPE_UNIX,     "Unix" },
        { OSTYPE_BEOS,     "BeOS" },
        { OSTYPE_MACOS,    "MacOS" },
        { OSTYPE_MACOSX,   "MacOS X" },
    };

    system->consprintf("\nPowered by Allegro %s %s,"
    " release %i/%i/%i\n", ALLEGRO_VERSION_STR, ALLEGRO_PLATFORM_STR, (ALLEGRO_DATE /100) % 100,
    ALLEGRO_DATE % 100, ALLEGRO_DATE / 10000);

    system->consprint("\nInitializing main Allegro system...");

    if (install_allegro(SYSTEM_AUTODETECT, &errno, NULL) != 0)
    {
        system->giveError(BENG_ERROR_FATAL, "Could not initialize Allegro!\n%s",
        (strlen(allegro_error)) ? allegro_error : "Unknown error!");
    }

    this->guiMode = BENG_INTERFACE_LOADED_TEXT; // Message box errors may now be shown.

    // System information //

    system->consprint("\nSuccess!\n\nProcessor family: ");
    for (int i = 0; i < NUM_CPU_FAMILY; i++)
    {
        if (cpu_family == cpufamily[i].id)
        {
            system->consprintf(cpufamily[i].text);
            break;
        }
    }

    system->consprintf("\nVendor: %s\n\n", cpu_vendor);

    for (int i = 0; i < NUM_CPU_CAPS; i++)
    {
        if (cpu_capabilities & cpucaps[i].id)
        {
            textpos += strlen(cpucaps[i].text);

            if (rowused)
            {
                strcat(textrow, ", ");
                textpos += 2;
            }

            if (textpos > 50)
            {
                system->consprintf("%s\n", textrow);
                textpos = 0;
                textrow[0] = '\0';
                rowused = 0;
            }

            strcat(textrow, cpucaps[i].text);

            rowused = 1;

            if (i == NUM_CPU_CAPS - 1)
                system->consprintf("%s\n", textrow);
        }
    }

    system->consprint("\nOS: ");
    for (int i = 0; i < NUM_CPU_OS; i++)
    {
        if (os_type == cpuos[i].id)
        {
            system->consprintf(cpuos[i].text);
            if (os_version != -1)
                system->consprintf(" %i.%i", os_version, os_revision);
            if (os_multitasking)
                system->consprintf(" (multitasking)");
            break;
        }
    }

    get_desktop_resolution(&deskw, &deskh);
    system->consprintf("\nDesktop resolution: %ix%ix%i", deskw, deskh, desktop_color_depth());

    // Avoid "SDL_App Syndrome"
    set_window_title(this->system->gameName.string);

    // Doesn't do much yet. Will read custom config values at a later date. TODO: Config!
    system->consprint("\n\nGrabbing Allegro config...");

    set_config_file("benga4.ini");

    // Init any relevant subsystems (graphics, audio...)
    this->iGfx->init();
    this->iSfx->init();

    system->consprint("\nInstalling keyboard...");

    if (install_keyboard() != 0)
    {
        system->giveError(BENG_ERROR_FATAL, "Could not install the keyboard!\n%s",
        (strlen(allegro_error)) ? allegro_error : "Unknown error!");
    }

    system->consprint("\nInstalling timer...");

    if (install_timer() != 0)
    {
        system->giveError(BENG_ERROR_FATAL, "Could not install the timer!\n%s",
        (strlen(allegro_error)) ? allegro_error : "Unknown error!");
    }

    // Paging these would be bad. Don't let it happen.
    LOCK_VARIABLE(this->ticQueue_);
    LOCK_VARIABLE(this->tics_);
    LOCK_VARIABLE(this->fpsTics_);
    LOCK_VARIABLE(this->fpsLast_);
    LOCK_VARIABLE(this->fpsIndex_);
    LOCK_VARIABLE(this->fpsUpdate_);
    LOCK_FUNCTION(this->incrementTics_);
    LOCK_FUNCTION(this->incrementFpsTics_);

    system->consprintf("\nCreating timer interrupts...\nGame framerate: %ihz", system->framerate);
    if (system->framerate >=50)
    {
        system->consprint("\n  Note that using a different monitor frequency will"
                            "\n  result in a less smooth display.");
    }

    // install_param_int must be used instead of install_int to get things working nicely in C++.
    install_param_int_ex(BengInterface::incrementTics_, (void*)this, BPS_TO_TIMER(system->framerate));
    install_param_int_ex(BengInterface::incrementFpsTics_, (void*)this, SECS_TO_TIMER(1));
    // Thanks, Kitty Cat

    this->isInit = true;

    system->consprint("\n\nInit successful!\n");

    return 1;
}

// Shuts all related systems down
void BengInterface::deinit(void)
{
    BengSystem *system = this->system;

    if (!this->isInit)
        return;

    this->guiMode = BENG_INTERFACE_UNLOADED;

    system->consprint("\n\nDeinitializing Allegro...");

    this->iSfx->deinit();
    this->iGfx->deinit();
    allegro_exit();

    this->isInit = false;

    system->consprint("\nDeinit complete!\n");

}

// Sets window title. Simple as. :)
void BengInterface::updateName_(void)
{
    if (this->isInit)
        set_window_title(this->system->gameName.string);
}

void BengInterface::displayError_(int severity, char *errorstring)
{
    switch (this->guiMode)
    {
        case BENG_INTERFACE_UNLOADED:
            return;

        default:
            switch (severity)
            {
                case BENG_ERROR_FATAL:

                    allegro_message("Encountered a fatal error!\n\n%s", errorstring);
                    break;

                case BENG_ERROR_STANDARD:

                    allegro_message("Encountered an error!\n\n%s", errorstring);
                    break;

                case BENG_ERROR_WARNING:

                    allegro_message("Warning!\n\n%s", errorstring);
                    break;

            }

    }

}

// Increments the timer variables, thus advancing one frame.
void BengInterface::incrementTics_(void *param)
{
    BengInterface *interface = (BengInterface*)param; // Thanks, Kitty Cat

    interface->ticQueue_++;
    interface->tics_++;
}

END_OF_FUNCTION(BengInterface::incrementTics_)

// Increments the FPS timer system.
void BengInterface::incrementFpsTics_(void *param)
{
    BengInterface *interface = (BengInterface*)param; // Thanks, Kitty Cat

    interface->fpsLast_ = interface->fpsTics_;
    interface->fpsTics_ = 0;

    if (interface->fpsIndex_ + 1 < FPS_NUM_VALUES)
    {
        interface->fpsIndex_++;
        interface->fpsUpdate_ = FPS_UPDATE;
    }
    else
    {
        interface->fpsIndex_ = 0;
        interface->fpsUpdate_ = FPS_NEWLINE;
    }

}

END_OF_FUNCTION(BengInterface::incrementFpsTics_)

// Main timing code that decides what should be processed (and rests if applicable)
void BengInterface::startLoop(std::bitset<2> &result)
{

    // If there is an FPS update, process it now.
    if (this->fpsUpdate_)
    {
        printf("%i ", this->fpsLast_); // Print out number

        // After every X values, average them together and start a new line
        if (this->fpsUpdate_ == FPS_NEWLINE)
        {
            printf(" = %.2f avg FPS\n", this->fpsTotalTics_ / (FPS_NUM_VALUES + 0.0));
            this->fpsTotalTics_ = 0;
        }

        // We don't want this processing again without an actual update.
        this->fpsUpdate_ = 0;
    }

    while (this->ticQueue_ <= 0)
    {
        rest(1);
    }

    // If there are no tics left to process and the queue is not empty,
    //  move the queue's count into ticsCurrent_ to countdown again.
    if (this->ticsCurrent_ <= 0 && this->ticQueue_ > 0)
    {
        this->ticsCurrent_ = this->ticQueue_;
        this->ticQueue_ = 0;
    }
    // If there are tics left to process, run one tic and countdown.
    if (this->ticsCurrent_ > 0)
    {
        result[BENG_INTERFACE_TIMER_PROCESS] = true;

        this->ticsCurrent_--;

        // You're too sloooow! If game logic is stupidly halting up the program, relieve its
        //  burden and stop trying to process skipped frames, lest the game logic suddenly lessens
        //  and it starts a giant game of catchup. No good. Just give up already.
        if (this->ticQueue_ >= 2)
        {
            this->ticsCurrent_ = 0;
            if (this->iGfx->tooSlow == -1) // One-time log message
                this->system->consprint("\nCAUTION!\nThe game has just throttled frameskip due to slow"
                                            "\ngame logic performance! Check your code for any problems!"
                                            "\nAlso, be sure that you aren't using too many objects!\n");
            this->iGfx->tooSlow = 3; // Also, annoy the user by displaying SLOW in the corner.
        }
    }
    if (this->ticsCurrent_ <= 0)
    {
        // Always update the screen. TODO: Config + option to enable/disable
        result[BENG_INTERFACE_TIMER_DRAW] = true;
    }

    if (key[KEY_ESC])
        this->system->exitLoop_ = 1;
}

int BengInterface::getTics(void)
{
    return this->tics_;
}
